#include "entry.h"

#undef LOG_TAG
#define LOG_TAG "AVCodecSamplePlayer"

struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;
    int32_t resultCode = -1;
    std::string resultStr = "";
    SampleInfo sampleInfo;
};

void DealCallBack(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    napi_value videoWidth;
    napi_create_int32(env, asyncCallbackInfo->sampleInfo.videoWidth, &videoWidth);
    napi_value videoHeight;
    napi_create_int32(env, asyncCallbackInfo->sampleInfo.videoHeight, &videoHeight);
    napi_value durationTime;
    napi_create_int64(env, asyncCallbackInfo->sampleInfo.durationTime, &durationTime);
    napi_value code;
    napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
    napi_value message;
    napi_create_string_utf8(env, asyncCallbackInfo->resultStr.data(), NAPI_AUTO_LENGTH, &message);
    napi_value obj;
    napi_create_object(env, &obj);

    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "videoWidth", videoWidth);
    napi_set_named_property(env, obj, "videoHeight", videoHeight);
    napi_set_named_property(env, obj, "durationTime", durationTime);
    napi_set_named_property(env, obj, "message", message);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
}

void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code, std::string str) {
    asyncCallbackInfo->resultCode = code;
    asyncCallbackInfo->resultStr = str;
}

void VideoInfoCallback(AsyncCallbackInfo *asyncCallbackInfo, int32_t code, SampleInfo sampleInfo) {
    asyncCallbackInfo->resultCode = code;
    asyncCallbackInfo->sampleInfo.videoWidth = sampleInfo.videoWidth;
    asyncCallbackInfo->sampleInfo.videoHeight = sampleInfo.videoHeight;
    asyncCallbackInfo->sampleInfo.durationTime = sampleInfo.durationTime;
    AVCODEC_SAMPLE_LOGI(" resultCode is %{public}i", asyncCallbackInfo->resultCode);
}

void InitializePlayer(napi_env env, void *data)
{
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    int32_t ret = Player::GetInstance().Init(asyncCallbackInfo->sampleInfo);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "Player init failed");
    } else {
        AVCODEC_SAMPLE_LOGI("Video width is %{public}i, height is %{public}i, durationTime is %{public}li", asyncCallbackInfo->sampleInfo.videoWidth,
                            asyncCallbackInfo->sampleInfo.videoHeight, asyncCallbackInfo->sampleInfo.durationTime);
        VideoInfoCallback(asyncCallbackInfo, 0, asyncCallbackInfo->sampleInfo);
    }
}

void Play(napi_env env, void *data) {
    AsyncCallbackInfo *asyncCallbackInfo = static_cast<AsyncCallbackInfo *>(data);
    int32_t ret = Player::GetInstance().Start();
    ret = Player::GetInstance().WaitForDone();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "Player start failed");
    } else {
        SetCallBackResult(asyncCallbackInfo, 0, "Player start finish");
    }
}

napi_value Entry::InitVideo(napi_env env, napi_callback_info info)
{
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    size_t argc = 3;
    napi_value args[3] = {nullptr};
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), nullptr,
        "GetContext napi_get_cb_info failed");
    SampleInfo sampleInfo;
    int index = 0;
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_value_int32(env, args[index++], &sampleInfo.inputFd), nullptr,
        "Initialize: Get value failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_value_int64(env, args[index++], &sampleInfo.inputFileOffset),
        nullptr, "Initialize: Get value failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_value_int64(env, args[index++], &sampleInfo.inputFileSize),
        nullptr, "Initialize: Get value failed");
    sampleInfo.window = NativeXComponentSample::PluginManager::GetInstance()->pluginWindow_;
    
    napi_value promise;
    napi_deferred deferred;
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_create_promise(env, &deferred, &promise), nullptr,
        "Initialize: Create promise failed");
    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;
    asyncCallbackInfo->sampleInfo = sampleInfo;
    napi_value resourceName;
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_create_string_latin1(env, "Initialize", NAPI_AUTO_LENGTH, &resourceName),
        nullptr, "Initialize: Create resourceName failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_create_async_work(env, nullptr, resourceName,
        [](napi_env env, void *data) { InitializePlayer(env, data); },
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); },
        (void *)asyncCallbackInfo, &asyncCallbackInfo->asyncWork), nullptr, "Initialize: Create async work failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_queue_async_work(env, asyncCallbackInfo->asyncWork), nullptr,
        "Initialize: queue_async_work failed");
    return promise;
}

napi_value Entry::StartVideo(napi_env env, napi_callback_info info)
{
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    napi_deferred deferred;
    napi_value promise;
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_create_promise(env, &deferred, &promise), nullptr,
                             "Start: Create promise failed");
    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;
    napi_value resourceName;
    CHECK_AND_RETURN_RET_LOG(napi_ok ==
                                 napi_create_string_latin1(env, "Start", NAPI_AUTO_LENGTH, &resourceName),
                             nullptr, "Start: Create resourceName failed");
    CHECK_AND_RETURN_RET_LOG(
        napi_ok == napi_create_async_work(
                       env, nullptr, resourceName, [](napi_env env, void *data) { Play(env, data); },
                       [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); },
                       (void *)asyncCallbackInfo, &asyncCallbackInfo->asyncWork),
        nullptr, "Initialize: Create async work failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_queue_async_work(env, asyncCallbackInfo->asyncWork), nullptr,
                             "Start: queue_async_work failed");
    return promise;
}

napi_value Entry::ResumeVideo(napi_env env, napi_callback_info info)
{
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    int32_t ret = Player::GetInstance().Resume();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        AVCODEC_SAMPLE_LOGI("Player pause failed.");
    }
    return nullptr;
}

napi_value Entry::PauseVideo(napi_env env, napi_callback_info info)
{
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    int32_t ret = Player::GetInstance().Pause();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        AVCODEC_SAMPLE_LOGI("Player pause failed.");
    }
    return nullptr;
}

napi_value Entry::VideoSeek(napi_env env, napi_callback_info info) {
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    int64_t currentTime = 0;
    size_t argc = 1;
    napi_value args[1] = {0};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int64(env, args[0], &currentTime);

    int32_t ret = Player::GetInstance().Seek(currentTime);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "PlayerSample video seek failed");
    }
    return nullptr;
}

napi_value Entry::Kill(napi_env env, napi_callback_info info) {
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    Player::GetInstance().Kill();
    return nullptr;
}

napi_value Entry::Loop(napi_env env, napi_callback_info info) {
    CHECK_AND_RETURN_RET_LOG(env != nullptr && info != nullptr, nullptr, "env or info is null");
    bool isLoop = false;
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), nullptr,
                             "GetContext napi_get_cb_info failed");
    CHECK_AND_RETURN_RET_LOG(napi_ok == napi_get_value_bool(env, args[0], &isLoop), nullptr,
                             "InitRecorder: Get value failed");
    int32_t ret = Player::GetInstance().Loop(isLoop);
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "Player loop failed");
    }
    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "initVideo", nullptr, Entry::InitVideo, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "startVideo", nullptr, Entry::StartVideo, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "resumeVideo", nullptr, Entry::ResumeVideo, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "pauseVideo", nullptr, Entry::PauseVideo, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "videoSeek", nullptr, Entry::VideoSeek, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "kill", nullptr, Entry::Kill, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "loop", nullptr, Entry::Loop, nullptr, nullptr, nullptr, napi_default, nullptr },
    };
    NativeXComponentSample::PluginManager::GetInstance()->Export(env, exports);
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module avcodecplayerModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "avcodecplayer",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterAVCodecPlayerModule(void)
{
    napi_module_register(&avcodecplayerModule);
}

